﻿/* cpcPaChi;

   Un compilador de Pascal chiquitito para CPC
   Por Nacho Cabanes - Version en C#

   Clase "CodeGen" (generador de código)

   Versiones hasta la fecha:

   Num.    Fecha       Cambios
   ---------------------------------------------------
   0.15cs  01-Dic-2010  Añade las órdenes CPCINK y WRITESTRING
   0.14cs  17-Nov-2010  Anadido "GenRetorno" para el retorno
                        de los PROCEDURE y "GenLlamada" para
                        saltar a ellos.
   0.13cs  14-Nov-2010  Genera codigo para DEC y para comentarios
                        GenIF hace las comparaciones "naturales",
                          no las contrarias (más aplicables a IF-THEN)
                          que hacía en la versión 0.12cs
   0.12cs  10-Nov-2010  Genera codigo para IF-THEN-ELSE
   0.11cs  08-Nov-2010  Genera código para para saltos
                        Se puede dar una segunda pasada, para reemplazar
                        las etiquetas en los saltos hacia adelante
   0.10cs  06-Nov-2010  Añadido "GenLecturaValor", que genera el código
                        para asignar un valor desde constante numérica,
                        constante con nombre o variable. Añadido INC
   0.06cs  29-Oct-2010  Incluye las ordenes PEN, PAPER, CPCMODE
   0.04cs  25-Oct-2010  Primera version del generador de codigo:
                        genera codigo para CLS, LOCATE, WRITECHAR
                        y el codigo final del cargador BASIC
                        para un codigo maquina que estara dentro
                        de ordenes DATA.

*/

using System;
using System.IO;

public class CodeGen
{
  StreamWriter ficheroDestino;
  int lineaActual = 10;
  int longitudTotal = 0;
  TablaSimbolos miTablaSimb;
  Scanner miScanner;
  bool ficheroAbierto = false;
  bool necesariaSegundaPasada = false;
  string nombre;
  /* Posicion inicial del codigo resultante,
     - Se podra cambiar con un parametro más adelante */
  int posicionInicialObj = 30000;
  int posicionInicialEjec = 30100;  // 100 bytes para variables
  int posicionInicialVars = 30004;  // 3 bytes previos para salto al codigo

  /* Constructor: abre el fichero de salida */
  public CodeGen(string nombreFichero, TablaSimbolos tabla, Scanner lexer)
  {
    miTablaSimb = tabla;
    miScanner = lexer;
    nombre = nombreFichero;
    ficheroDestino = File.CreateText( nombreFichero );
    ficheroAbierto = true;

    miTablaSimb.IndicarComienzo(posicionInicialVars);

    miTablaSimb.Insertar("INICIOPROGRAMA", tipoId.LABEL,
      tamDato.INTEGER, 0 );
    genGOTO("INICIOPROGRAMA");

    // Guardo las constantes de colores
    // http://en.wikipedia.org/wiki/List_of_8-bit_computer_hardware_palettes
    miTablaSimb.Insertar("BLACK", tipoId.CONST, tamDato.BYTE,0);
    miTablaSimb.Insertar("BLUE", tipoId.CONST, tamDato.BYTE,1);
    miTablaSimb.Insertar("LIGHTBLUE", tipoId.CONST, tamDato.BYTE,2);
    miTablaSimb.Insertar("RED", tipoId.CONST, tamDato.BYTE,3);
    miTablaSimb.Insertar("MAGENTA", tipoId.CONST, tamDato.BYTE,4);
    miTablaSimb.Insertar("VIOLET", tipoId.CONST, tamDato.BYTE,5);
    miTablaSimb.Insertar("BRIGHTRED", tipoId.CONST, tamDato.BYTE,6);
    miTablaSimb.Insertar("PURPLE", tipoId.CONST, tamDato.BYTE,7);
    miTablaSimb.Insertar("BRIGHTMAGENTA", tipoId.CONST, tamDato.BYTE,8);
    miTablaSimb.Insertar("GREEN", tipoId.CONST, tamDato.BYTE,9);
    miTablaSimb.Insertar("CYAN", tipoId.CONST, tamDato.BYTE,10);
    miTablaSimb.Insertar("SKYBLUE", tipoId.CONST, tamDato.BYTE,11);
    miTablaSimb.Insertar("YELLOW", tipoId.CONST, tamDato.BYTE,12);
    miTablaSimb.Insertar("GREY", tipoId.CONST, tamDato.BYTE,13);
    miTablaSimb.Insertar("PALEBLUE", tipoId.CONST, tamDato.BYTE,14);
    miTablaSimb.Insertar("ORANGE", tipoId.CONST, tamDato.BYTE,15);
    miTablaSimb.Insertar("PINK", tipoId.CONST, tamDato.BYTE,16);
    miTablaSimb.Insertar("PALEMAGENTA", tipoId.CONST, tamDato.BYTE,17);
    miTablaSimb.Insertar("BRIGHTGREEN", tipoId.CONST, tamDato.BYTE,18);
    miTablaSimb.Insertar("SEAGREEN", tipoId.CONST, tamDato.BYTE,19);
    miTablaSimb.Insertar("BRIGHTCYAN", tipoId.CONST, tamDato.BYTE,20);
    miTablaSimb.Insertar("LIMEGREEN", tipoId.CONST, tamDato.BYTE,21);
    miTablaSimb.Insertar("PALEGREEN", tipoId.CONST, tamDato.BYTE,22);
    miTablaSimb.Insertar("PALECYAN", tipoId.CONST, tamDato.BYTE,23);
    miTablaSimb.Insertar("BRIGHTYELLOW", tipoId.CONST, tamDato.BYTE,24);
    miTablaSimb.Insertar("PALEYELLOW", tipoId.CONST, tamDato.BYTE,25);
    miTablaSimb.Insertar("WHITE", tipoId.CONST, tamDato.BYTE,26);

  }

  /* Destructor: cierra el fichero de salida si hay error en el proceso */
  ~CodeGen()
  {
    if (ficheroAbierto)
      ficheroDestino.Close();
  }


  /* Generar la orden de lectura en la tabla de símbolos para una
     constante o variable, o cargar un valor directo, y finalmente
     guardarlo en el registro A */
  public void GenLecturaValor(string identif)
  {
    // Primero miro si es número
    if ( (identif[0] >= '0') && (identif[0] <= '9') )
    {
      int numero = Convert.ToInt32( identif );
      ficheroDestino.WriteLine(lineaActual
          + " DATA 3E," + hex(numero)
          + ": ' LD A, " + numero );
        lineaActual += 10; longitudTotal += 2;
    }
    // Si no es número, miro si es constante
    else if (miTablaSimb.LeerTipo( identif ) == tipoId.CONST)
    {
      int valor = miTablaSimb.LeerValor( identif );
      ficheroDestino.WriteLine(lineaActual
          + " DATA 3E," + hex(valor)
          + ": ' LD A, " + valor + " - Constante: " +identif );
        lineaActual += 10; longitudTotal += 2;
    }
    // Si no es número ni constante, debería ser variable
    else if (miTablaSimb.LeerTipo( identif ) == tipoId.VAR)
    {
      int direccion = miTablaSimb.LeerValor( identif );
      ficheroDestino.WriteLine(lineaActual
          + " DATA 3A," + hex(direccion % 256) +"," + hex(direccion / 256)
          + ": ' LD A, (" + direccion + ") - Variable: " +identif );
        lineaActual += 10; longitudTotal += 3;
    }
    // Si tampoco es variable, algo va mal
    else miScanner.ErrorFatal("Asignación incorrecta");

  }


  // Auxiliar, para convertir a hexadecimal de forma rápida
  string hex(int valor)
  {
    string hexTemp = Convert.ToString(valor,16).ToUpper();
    if (hexTemp.Length <= 1)
      hexTemp = "0" + hexTemp;
    return hexTemp;
  }



  /* Generar codigo para asignación */
  public void genASIGN(string identif, string valor)
  {
    GenLecturaValor(valor);
    int direccion = miTablaSimb.LeerValor( identif );
    ficheroDestino.WriteLine(lineaActual
      + " DATA 32,"
      + hex(direccion % 256) +","
      + hex(direccion / 256)
      + ": ' LD (" + direccion + "), A - Asignacion: "
      +identif +"="+valor );
    lineaActual += 10; longitudTotal += 3;
  }

  /* Generar codigo para CLS */
  public void genCLS()
  {
    ficheroDestino.WriteLine(lineaActual+" DATA CD,6C,BB: ' CALL &BB6C - CLS" );
    lineaActual += 10; longitudTotal += 3;
  }


  /* Generar un comentario en el fichero de destino */
  public void genComentario(string texto)
  {
    ficheroDestino.WriteLine(lineaActual+" ' "+texto );
    lineaActual += 10;
  }



  /* Generar codigo para CpcInk -tinta- */
  public void genCPCINK(string tinta, string color1, string color2)
  {
    GenLecturaValor( color1 );
    // Copio el primer color de A a B
    ficheroDestino.WriteLine(lineaActual+" DATA 47: ' LD B, A" );
    lineaActual += 10;

    GenLecturaValor( color2 );
    // Copio el segundo color de A a C
    ficheroDestino.WriteLine(lineaActual+" DATA 4F: ' LD C, A" );
    lineaActual += 10;

    // Y guardo la tinta en A
    GenLecturaValor( tinta );

    ficheroDestino.WriteLine(lineaActual+
        " DATA CD,32,BC: ' CALL &BC32 - SET INK" );
    lineaActual += 10;
    longitudTotal += 5;
  }

  /* Generar codigo para CPCMODE */
  public void genCPCMODE()
  {
      ficheroDestino.WriteLine(lineaActual+
        " DATA CD,0E,BC: ' CALL &BC0E - SET MODE" );
      lineaActual += 10; longitudTotal += 3;
  }


  /* Generar codigo para CPCPAPER */
  public void genCPCPAPER()
  {
      ficheroDestino.WriteLine(lineaActual+
        " DATA CD,96,BB: ' CALL &BB96 - SET PAPER" );
      lineaActual += 10; longitudTotal += 3;
  }


  /* Generar codigo para CPCPEN */
  public void genCPCPEN()
  {
      ficheroDestino.WriteLine(lineaActual+
        " DATA CD,90,BB: ' CALL &BB90 - SET PEN" );
      lineaActual += 10; longitudTotal += 3;
  }


  /* Generar codigo para decremento  */
  public void genDEC(string identif, int valor)
  {
    if (miTablaSimb.LeerTipo( identif ) != tipoId.VAR)
      miScanner.ErrorFatal("Sólo se puede decrementar una variable");

    // Quiza fuera mas razonable usar INC (HL) -34h,
    // o ADD si es incremento grande
    GenLecturaValor( identif );

    for (int i=0; i<valor; i++)
    {
      ficheroDestino.WriteLine( lineaActual + " DATA 3D :' DEC A" );
      lineaActual += 10;  longitudTotal++;
    }

    int direccion = miTablaSimb.LeerValor( identif );
    ficheroDestino.WriteLine(lineaActual
      + " DATA 32,"
      + hex(direccion % 256) +","
      + hex(direccion / 256)
      + ": ' LD (" + direccion + "), A - Incremento: "
      +identif +"+"+valor );
    lineaActual += 10; longitudTotal += 3;
  }

  /* Generar codigo para GOTO (salto incondicional) */
  public void genGOTO(string identif)
  {
      int direccion = miTablaSimb.LeerValor( identif );
      if (direccion != 0)  // Si ya se sabe la dirección de salto
        ficheroDestino.WriteLine(lineaActual
          + " DATA C3,"
          + hex(direccion % 256) +","
          + hex(direccion / 256)
          + ": ' JP " +identif );
      else // Si no se sabe la dirección de salto: en segunda pasada
      {
        ficheroDestino.WriteLine(lineaActual
          + " DATA C3,"
          + "nnn"+identif.ToUpper()+"LO,"
          + "nnn"+identif.ToUpper()+"HI"
          + ": ' JP " +identif );
        necesariaSegundaPasada = true;
      }
      lineaActual += 10; longitudTotal += 3;
  }


  /* Generar codigo para la direccion de salto de un GOTO,
      pero sin el goto -para saltos condicionales- */
  public void genDireccSinGOTO(string identif)
  {
      int direccion = miTablaSimb.LeerValor( identif );
      if (direccion != 0)  // Si ya se sabe la dirección de salto
        ficheroDestino.WriteLine(lineaActual
          + " DATA "
          + hex(direccion % 256) +","
          + hex(direccion / 256)
          + ": ' direcc de salto " +identif );
      else // Si no se sabe la dirección de salto: en segunda pasada
      {
        ficheroDestino.WriteLine(lineaActual
          + " DATA "
          + "nnn"+identif.ToUpper()+"LO,"
          + "nnn"+identif.ToUpper()+"HI"
          +  ": ' direcc de salto " +identif );
        necesariaSegundaPasada = true;
      }
      lineaActual += 10; longitudTotal += 2;
  }

  /* Generar codigo para comparacion IF  */
  public void genIF(string ident1, string tipoComparacion,
    string ident2)
  {
    // Primero guardo ident2 en B
    ficheroDestino.WriteLine(lineaActual+" ' IF "+ident1
      + tipoComparacion + ident2 );
    lineaActual += 10;
    GenLecturaValor(ident2);
    ficheroDestino.WriteLine(lineaActual+" DATA 47:' LD B, A ("+ident2+")" );
    lineaActual += 10;
    longitudTotal ++;

    // Luego guardo ident1 en A
    GenLecturaValor(ident1);

    // Ahora preparo la comparacion
    ficheroDestino.WriteLine(lineaActual + " DATA B8:' CP B" );
    lineaActual += 10;
    if (tipoComparacion == "<>")
      ficheroDestino.WriteLine(lineaActual + " DATA C2:' JP NZ" );
    else if (tipoComparacion == "=")
      ficheroDestino.WriteLine(lineaActual + " DATA CA:' JP Z" );
    else if (tipoComparacion == "<")
      ficheroDestino.WriteLine(lineaActual + " DATA FA:' JP M" );
    else if (tipoComparacion == "<=")   /* Traduccion incorrecta aun ### */
      ficheroDestino.WriteLine(lineaActual + " DATA FA:' JP M" );
    else if (tipoComparacion == ">")
      ficheroDestino.WriteLine(lineaActual + " DATA F2:' JP P" );
    else if (tipoComparacion == ">=")   /* Traduccion incorrecta aun ### */
      ficheroDestino.WriteLine(lineaActual + " DATA F2:' JP P" );
    else
      miScanner.ErrorFatal("Comparacion incorrecta");
    lineaActual += 10;
    longitudTotal += 2;
    necesariaSegundaPasada = true;
  }



  /* Generar codigo para incremento  */
  public void genINC(string identif, int valor)
  {
    if (miTablaSimb.LeerTipo( identif ) != tipoId.VAR)
      miScanner.ErrorFatal("Sólo se puede incrementar una variable");

    // Quiza fuera mas razonable usar INC (HL) -34h,
    // o ADD si es incremento grande
    GenLecturaValor( identif );

    for (int i=0; i<valor; i++)
    {
      ficheroDestino.WriteLine( lineaActual + " DATA 3C :' INC A" );
      lineaActual += 10;  longitudTotal++;
    }

    int direccion = miTablaSimb.LeerValor( identif );
    ficheroDestino.WriteLine(lineaActual
      + " DATA 32,"
      + hex(direccion % 256) +","
      + hex(direccion / 256)
      + ": ' LD (" + direccion + "), A - Incremento: "
      +identif +"+"+valor );
    lineaActual += 10; longitudTotal += 3;
  }


  /* Generar codigo para LABEL */
  public void genLABEL(string etiqueta)
  {
      int posicion = posicionInicialEjec + longitudTotal;
      ficheroDestino.WriteLine(lineaActual+
        " ' Etiqueta de salto: " + etiqueta + ", posicion: " + posicion);
      lineaActual += 10;
      miTablaSimb.CambiarValor( etiqueta, posicion );
  }


  /* Generar codigo para llamada a una subrutina -sin direccion- */
  public void genLLamada()
  {
      ficheroDestino.WriteLine(lineaActual+
        " DATA CD: ' CALL - Llamada a proced." );
      lineaActual += 10;  longitudTotal ++;
  }


  /* Generar codigo para LOCATE */
  public void genLOCATE1()
  {
      // Código para la columna
      ficheroDestino.WriteLine(lineaActual+
        " DATA CD,6F,BB: ' CALL &BB6F - CURSOR COLUMN" );
      lineaActual += 10;  longitudTotal += 3;
  }
  public void genLOCATE2()
  {
      // Código para la fila
      ficheroDestino.WriteLine(lineaActual+
        " DATA CD,72,BB: ' CALL &BB72 - CURSOR ROW" );
      lineaActual += 10;  longitudTotal += 3;
  }


  /* Generar codigo de retorno tras un procedimiento */
  public void genRetorno()
  {
      ficheroDestino.WriteLine(lineaActual+
        " DATA C9: ' RET - Retorno de proced." );
      lineaActual += 10;  longitudTotal ++;
  }


  /* Generar codigo para WRITECHAR */
  public void genWRITECHAR( char letra )
  {
      byte caracter = Convert.ToByte(letra);
      ficheroDestino.WriteLine(lineaActual
        + " DATA 3E," + hex(caracter)
        + ": ' LD A, \"" + letra +"\"");
      lineaActual += 10;
      ficheroDestino.WriteLine(lineaActual+
        " DATA CD,5A,BB: ' CALL &BB5A - WRITECHAR" );
      lineaActual += 10;
      longitudTotal += 5;
  }


  public void genWRITESTRING(string texto)
  {
    byte i;
    //string direccionCadena;

    genComentario("-- Escritura de cadena");
    necesariaSegundaPasada = true;

    // Como identificador asoiado, uso el texto sin espacios
    // y convertido a mayusculas
    string identif = texto.ToUpper().Replace(" ", "");
    /*insertarSimboloLabel("txt"+texto);
    insertarSimboloLabel("saltoIni"+texto);
    insertarSimboloLabel("saltoRep"+texto);
    insertarSimboloLabel("fin"+texto);*/

    // Preparo los saltos que todavía no están definidos
    miTablaSimb.Insertar("saltoIni"+identif, tipoId.LABEL,
      tamDato.INTEGER, 0 );
    miTablaSimb.Insertar("fin"+identif, tipoId.LABEL,
      tamDato.INTEGER, 0 );




    genGOTO("saltoIni"+identif);
    //genLABEL("txt"+texto);
    miTablaSimb.Insertar("txt"+identif, tipoId.LABEL,
      tamDato.INTEGER, GetDireccionActual() );
    genComentario("txt"+identif+": "+GetDireccionActual());
    // Datos de la cadena
    ficheroDestino.Write( lineaActual+" DATA ");
    for (i=0; i<texto.Length; i++)
      ficheroDestino.Write( hex (Convert.ToByte(texto[i]))+ ",");
    ficheroDestino.WriteLine( "00: 'Texto: "+ texto);
    lineaActual += 10;
    longitudTotal = longitudTotal + texto.Length + 1;

    // Rutina repetitiva de escritura

    //genLABEL("saltoIni"+texto);
    genComentario("saltoIni"+identif+": "+GetDireccionActual());
    miTablaSimb.CambiarValor("saltoIni"+identif, GetDireccionActual() );
    ficheroDestino.WriteLine(lineaActual+" DATA 11: ' LD DE, texto" );
    lineaActual += 10; longitudTotal ++;
    genDireccSinGOTO("txt"+identif);

    //genLABEL("saltoRep"+texto);
    genComentario("saltoRep"+identif+": "+GetDireccionActual());
    miTablaSimb.Insertar("saltoRep"+identif, tipoId.LABEL,
      tamDato.INTEGER, GetDireccionActual() );
    ficheroDestino.WriteLine(lineaActual+" DATA 1A: ' LD A, (DE)" );
    lineaActual += 10; longitudTotal ++;

    ficheroDestino.WriteLine(lineaActual+" DATA B7: ' OR A" );
    lineaActual += 10; longitudTotal ++;

    ficheroDestino.WriteLine(lineaActual+" DATA CA: ' JP Z - Salir" );
    lineaActual += 10; longitudTotal ++;
    genDireccSinGOTO("fin"+identif);

    ficheroDestino.WriteLine(lineaActual+" DATA CD,5A,BB: 'CALL &BB5A - WRITECHAR" );
    lineaActual += 10; longitudTotal +=3;

    ficheroDestino.WriteLine(lineaActual+" DATA 13: ' INC DE" );
    lineaActual += 10; longitudTotal ++;

    genGOTO("saltoRep"+identif);

    genLABEL("fin"+identif);
    //miTablaSimb.CambiarValor("fin"+identif, GetDireccionActual() );
    genComentario("-- Fin de escritura de cadena");
  }


  /* Direccion de codigo actual, para las llamadas a procedimientos */
  public int GetDireccionActual()
  {
    return posicionInicialEjec + longitudTotal;
  }


  /* Segunda pasada: si hay saltos adelante, a direcciones indeterminadas */
  void segundaPasada()
  {
    string linea;
    string nuevoNombre = nombre.Substring(0, nombre.IndexOf(".") )
      + "_2p.bas";

    StreamReader entrada = File.OpenText(nombre);
    StreamWriter salida = File.CreateText(nuevoNombre);
    linea = entrada.ReadLine();
    while (linea != null)
    {
      if (linea.IndexOf("nnn") >= 0)  // Si contiene simbolos a sustituir
      {
        for (int i=0; i<miTablaSimb.GetNumSimbolos(); i++)
        {
          string nombreVar1 = "nnn" + miTablaSimb.GetSimbolo(i)+"LO";
          string nombreVar2 = "nnn" + miTablaSimb.GetSimbolo(i)+"HI";
          if (linea.IndexOf( nombreVar1 ) >= 0)
            linea = linea.Replace( nombreVar1,
              hex(miTablaSimb.GetValor(i) % 256) ); // LO = Byte menos signif.
          if (linea.IndexOf( nombreVar2 ) >= 0)
            linea = linea.Replace( nombreVar2,
              hex(miTablaSimb.GetValor(i) / 256) ); // HI = Byte mas signif.
        }
      }
      salida.WriteLine(linea);
      linea = entrada.ReadLine();
    }
    entrada.Close();
    salida.Close();
  }


  /* Genera el codigo de destino final: cargador Basic */
  public void genCodigoFinal()
  {
    ficheroDestino.WriteLine(lineaActual+" DATA C9: ' RET" );
    ficheroDestino.WriteLine(lineaActual+10+" longitud = "+longitudTotal );

    ficheroDestino.WriteLine(lineaActual+20 + " MEMORY "+ (posicionInicialObj-1) );
    ficheroDestino.WriteLine(lineaActual+30 + " 'Codigo del programa" );
    ficheroDestino.WriteLine(lineaActual+40 + " FOR n=" +posicionInicialEjec+
      " TO "+posicionInicialEjec+"+longitud" );
    ficheroDestino.WriteLine(lineaActual+50 + "   READ a$:POKE n,VAL(\"&\"+a$)" );
    ficheroDestino.WriteLine(lineaActual+60 + " NEXT" );
    ficheroDestino.WriteLine(lineaActual+70 + " 'Espacio para variables");
    ficheroDestino.WriteLine(lineaActual+80 + " FOR n=" + posicionInicialObj +
      " TO " + (posicionInicialEjec-1) );
    ficheroDestino.WriteLine(lineaActual+90 + "   POKE n,0" );
    ficheroDestino.WriteLine(lineaActual+100 + " NEXT" );
    ficheroDestino.WriteLine(lineaActual+110 + " POKE "+posicionInicialObj
      +", &CD: 'inicio: CALL "+posicionInicialEjec );
    ficheroDestino.WriteLine(lineaActual+120 + " POKE "+ (posicionInicialObj+1)
      +", &"+ hex(posicionInicialEjec % 256 ) );
    ficheroDestino.WriteLine(lineaActual+130 + " POKE "+ (posicionInicialObj+2)
      +", &"+ hex(posicionInicialEjec / 256 ) );
    ficheroDestino.WriteLine(lineaActual+140 + " CALL "+posicionInicialObj );
    ficheroDestino.Close();
    ficheroAbierto = false;  // Para no cerrar dos veces en el constructor

    if (necesariaSegundaPasada)
      segundaPasada();

  }

}
